package fimuni.pb138.repositar;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.basex.core.cmd.Open;
import org.basex.server.ClientQuery;
import org.basex.server.ClientSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

@Service
public class WARService {

	private ClientSession session;
	private static final Logger logger = LoggerFactory
			.getLogger(WARService.class);

	/**
	 * Constructor for WSDLService
	 * Establishes connection to BaseX database
	 */
	public WARService() {
		try {
			session = new ClientSession(ArtifactUtils.DATABASE_SERVER,
					ArtifactUtils.DATABASE_PORT,
					ArtifactUtils.DATABASE_USERNAME,
					ArtifactUtils.DATABASE_PASSWORD);
			session.execute(new Open(ArtifactUtils.DATABASE_NAME));
		} catch (IOException e) {
			logger.error(e.getLocalizedMessage());
		}
	}

	/**
	 * Inserts WAR file into database. Creates new version of file If it already exist in DB, 
	 * or creates new record for the file
	 * 
	 * @param file file to be inserted into DB
	 * @return
	 */
	public String insertWar(WARFile file) {
		try {
			ClientQuery cq = session.query("count(/wars/war[name=\""
					+ file.getName() + "\"])");
			boolean exists = !cq.execute().equals("0");
			if (exists) {
				StringBuilder query = new StringBuilder();
				query.append("insert node");
				cq = session.query("max(/wars/war[name=\"" + file.getName()
						+ "\"]/versions/version/@number)");
				int lastVersionNumber = Integer.valueOf(cq.execute()) + 1;
				query.append("<version number=\"" + lastVersionNumber
						+ "\" fileName=\"" + file.getFileName() + "\">");
				query.append("<listeners>");
				for (String listener : file.getListeners()) {
					query.append("<listener>" + listener + "</listener>");
				}
				query.append("</listeners>");
				query.append("<filters>");
				for (String filter : file.getFilters()) {
					query.append("<filter>" + filter + "</filter>");
				}
				query.append("</filters>");
				query.append("<webxml>" + file.getWebXML() + "</webxml>");
				query.append("</version>");
				query.append("into /wars/war[name=\"" + file.getName()
						+ "\"]/versions");
				cq = session.query(query.toString());
			} else {
				StringBuilder query = new StringBuilder();
				query.append("insert node");
				cq = session.query("max(/wars/war/@id)");
				int lastId = Integer.valueOf(cq.execute()) + 1;
				query.append("<war id=\"" + lastId + "\">");
				query.append("<name>" + file.getName() + "</name>");
				query.append("<versions>");
				query.append("<version number=\"1\" fileName=\""
						+ file.getFileName() + "\">");
				query.append("<listeners>");
				for (String listener : file.getListeners()) {
					query.append("<listener>" + listener + "</listener>");
				}
				query.append("</listeners>");
				query.append("<filters>");
				for (String filter : file.getFilters()) {
					query.append("<filter>" + filter + "</filter>");
				}
				query.append("</filters>");
				query.append("<webxml>" + file.getWebXML() + "</webxml>");
				query.append("</version>");
				query.append("</versions>");
				query.append("</war>");
				query.append("into /wars");
				cq = session.query(query.toString());
			}
			cq.execute();
		} catch (IOException e) {
			logger.error(e.getLocalizedMessage());
		}
		return "";
	}

	/**
	 * Finds artifact by id in database
	 * 
	 * @param id id of wanted artifact
	 * @return list of all versions of specified artifact
	 */
	public List<WARFile> findWARbyID(long id) {
		List<WARFile> warFiles = new ArrayList<>();
		try {
			ClientQuery cq = session.query("count(/wars/war[@id=\"" + id
					+ "\"]/versions/version)");
			int versionCount = Integer.parseInt(cq.execute());
			cq = session.query("/wars/war[@id=\"" + id + "\"]/name/text()");
			String name = cq.execute();
			for (int i = 1; i <= versionCount; i++) {
				WARFile warFile = new WARFile();
				warFile.setId(id);
				warFile.setName(name);
				warFile.setVersion(i);
				cq = session.query("string(/wars/war[@id=\"" + id
						+ "\"]/versions/version[" + i + "]/@fileName)");
				warFile.setFileName(cq.execute());

				List<String> listeners = new ArrayList<>();
				cq = session.query("/wars/war[@id=\"" + id
						+ "\"]/versions/version[" + i
						+ "]/listeners/listener/text()");
				while (cq.more()) {
					listeners.add(cq.next());
				}
				warFile.setListeners(listeners);

				List<String> filters = new ArrayList<>();
				cq = session.query("/wars/war[@id=\"" + id
						+ "\"]/versions/version[" + i
						+ "]/filters/filter/text()");
				while (cq.more()) {
					filters.add(cq.next());
				}
				warFile.setFilters(filters);

				cq = session.query("/wars/war[@id=\"" + id
						+ "\"]/versions/version[" + i + "]/webxml/text()");
				String webXML = cq.execute();
				warFile.setWebXML(webXML);

				warFiles.add(warFile);
			}

		} catch (IOException e) {
			logger.error(e.getLocalizedMessage());
		}
		return warFiles;
	}
	
	/**
	 * Finds artifact by id in database
	 * 
	 * @param id id of wanted artifact
	 * @param version version of wanted artifact
	 * @return version of specified artifact
	 */
	public WARFile findWARbyIDandVersion(long id, long version) {
		List<WARFile> warFiles = findWARbyID(id);
		for(WARFile file : warFiles) {
			if(file.getVersion() == version) {
				return file;
			}
		}
		
		return null;
	}

	/**
	 * Finds artifact by its metadata in database
	 * 
	 * @param name name of the artifact
	 * @param listener listener in WAR archive's web.xml
	 * @param filter filter in WAR archive's web.xml
	 * @return list of all versions of specified artifact
	 */
	public List<WARFile> findWARbyMetadata(String name, String listener,
			String filter) {
		if(name == null)
			name = "";
		if(listener == null)
			listener = "";
		if(filter == null)
			filter = "";
				
		List<WARFile> warFiles = getAllWARs();

		// filter all unwanted versions of specified artifact
		Iterator<WARFile> i = warFiles.iterator();
		while (i.hasNext()) {
			WARFile file = i.next();
			if( (!file.getName().equals(name) && !name.isEmpty())
					|| (!file.getListeners().contains(listener) && !listener.isEmpty()) 
					|| (!file.getFilters().contains(filter) && !filter.isEmpty())) {
				i.remove();
				continue;
			}
		}

		return warFiles;
	}
	
	/**
	 * Get all WAR files from database
	 * For each WAR, function returns object representing latest version of that file
	 * 
	 * @return list of all WAR files
	 */
	public List<WARFile> getAllWARs() {
		List<WARFile> WARList = new ArrayList<>();
		try {
			ClientQuery cq = session.query("count(/wars/war)");
			
			int warCount = Integer.parseInt(cq.execute());
			for(int i = 1; i <= warCount; i++) {
				List<WARFile> versionList = findWARbyID(i);
				WARFile war = versionList.get(versionList.size() - 1);
				WARList.add(war);
			}
		}
		catch(IOException e) {
			logger.error(e.getLocalizedMessage());
		}
		return WARList;
	}

	/**
	 * Stores uploaded file to database
	 * 
	 * @param file uploaded file
	 */
	public void processWar(MultipartFile file) {
		String fileName = ArtifactUtils.saveFile(file);
		if(fileName == null) {
			logger.error("failed to save file");
			return;
		}
		WARFile warFile = new WARFile();
		String oldName = file.getOriginalFilename();
		warFile.setName(oldName);
		String webXMLPath = extractWebXML(new File(ArtifactUtils.STORAGE_PATH + fileName));
		if(webXMLPath.isEmpty()) {
			logger.error("web.xml not found");
		}
		warFile.setWebXML(webXMLPath);
		warFile.setFileName(fileName);

		String filterNames = XQueryProcessor
				.processQuery(ArtifactUtils.XQUERY_WAR_FILTER.replace(
						"DOCNAME",
						ArtifactUtils.STORAGE_PATH.replace("\\", "/") + warFile.getWebXML()));
		List<String> filters;
		if (filterNames.isEmpty()) {
			filters = new ArrayList<>();
		} else {
			filters = new ArrayList<>(Arrays.asList(filterNames.split("\\|")));
		}
		warFile.setFilters(filters);

		String listenerNames = XQueryProcessor
				.processQuery(ArtifactUtils.XQUERY_WAR_LISTENER.replace(
						"DOCNAME",
						ArtifactUtils.STORAGE_PATH.replace("\\", "/") + warFile.getWebXML()));
		List<String> listeners;
		if (listenerNames.isEmpty()) {
			listeners = new ArrayList<>();
		} else {
			listeners = new ArrayList<>(Arrays.asList(listenerNames.split("\\|")));
		}
		warFile.setListeners(listeners);

		insertWar(warFile);
	}

	/**
	 * Extracts web.xml from WAR archive
	 * 
	 * @param file archive
	 * @return filename of stored web.xml
	 */
	private String extractWebXML(File file) {
		try {
			ZipFile war = new ZipFile(file.getAbsolutePath());
			String webXML = "web.xml";
			Enumeration<? extends ZipEntry> e = war.entries();
			boolean found = false;
			while (e.hasMoreElements()) {
				ZipEntry entry = (ZipEntry) e.nextElement();
				if (entry.getName().toLowerCase().indexOf(webXML) != -1) {
					found = true;
					InputStream is = war.getInputStream(entry);
					byte[] buffer = new byte[4096];
					int read = 0;
					File saved = File.createTempFile(
							ArtifactUtils.STORAGE_PREFIX, "", new File(
									ArtifactUtils.STORAGE_PATH));
					FileOutputStream fos = new FileOutputStream(saved);
					while ((read = is.read(buffer)) > 0) {
						fos.write(buffer, 0, read);
						fos.flush();
					}
					war.close();
					fos.close();
					return saved.getName();
				}
			}
			if (found == false) {
				logger.error("File :" + webXML
						+ " Not Found Inside Zip File: " + war.getName());
				war.close();
				return "";
			}
			war.close();
		} catch (IOException ioe) {
			logger.error("Error opening zip file " + ioe);
		}
		return null;
	}
}
